﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using JetBrains.DocumentModel;
using JetBrains.Metadata.Reader.API;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Hotspots;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.LiveTemplates;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Macros.Implementations;
using JetBrains.ReSharper.LiveTemplates;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Resolve.Managed;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
using JetBrains.ReSharper.Psi.Modules;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.TextControl;
using JetBrains.Util;

namespace MugenMvvmToolkit.ReSharper
{
    public static class Extensions
    {
        #region Methods

        [NotNull, Pure]
        public static string GetMemberName([NotNull] this ITypeParametersOwner typeElement)
        {
            if (typeElement == null) throw new ArgumentNullException("typeElement");
            if (typeElement.TypeParameters.Count == 0)
                return typeElement.ShortName;
            var builder = new StringBuilder();
            builder.Append(typeElement.ShortName);
            builder.Append("<");
            for (int index = 0; index < typeElement.TypeParameters.Count; index++)
            {
                builder.Append("T");
                builder.Append(index);
                builder.Append(",");
            }
            builder.Remove(builder.Length - 1, 1);
            builder.Append(">");
            return builder.ToString();
        }

        public static void AddIfNotNull<T>([NotNull] this ICollection<T> collection, [CanBeNull] T item)
        {
            if (collection == null) throw new ArgumentNullException("collection");
            if (item != null)
                collection.Add(item);
        }

        public static DocumentRange GetLiteralHighlightingRange([NotNull] this ICSharpLiteralExpression literal)
        {
            if (literal == null) throw new ArgumentNullException("literal");
            DocumentRange documentRange = literal.GetDocumentRange();
            var s = literal.ConstantValue.Value as string;
            if (string.IsNullOrEmpty(s))
                return documentRange;
            return new DocumentRange(documentRange.Document,
                new TextRange(documentRange.TextRange.StartOffset + 1, documentRange.TextRange.EndOffset - 1));
        }

        [CanBeNull]
        public static T FindParent<T>(this ITreeNode treeNode) where T : class, ITreeNode
        {
            while (treeNode != null)
            {
                var result = treeNode as T;
                if (result != null)
                    return result;
                treeNode = treeNode.Parent;
            }
            return null;
        }

        [CanBeNull]
        public static T FindChild<T>(this ITreeNode treeNode) where T : class, ITreeNode
        {
            if (treeNode == null)
                return null;
            T result = null;
            treeNode.ProcessDescendantsForResolve(new OneActionProcessor(node => result = node as T,
                node => result == null));
            return result;
        }

        [NotNull]
        public static IList<T> FindChilds<T>(this ITreeNode treeNode) where T : class, ITreeNode
        {
            if (treeNode == null)
                return EmptyList<T>.InstanceList;

            var result = new List<T>();
            treeNode.ProcessDescendantsForResolve(new OneActionProcessor(node => result.AddIfNotNull(node as T), node => true));
            return result;
        }

        [CanBeNull]
        public static HotspotSessionUi TryExecuteHotspotDefault([NotNull] this LiveTemplatesManager manager,
            ISolution solution, ITextControl control, params HotspotInfo[] hotspots)
        {
            if (manager == null)
                throw new ArgumentNullException("manager");
            if (hotspots.Length == 0)
                return null;
            return manager
                .CreateHotspotSessionAtopExistingText(solution, TextRange.InvalidRange, control,
                    LiveTemplatesManager.EscapeAction.LeaveTextAndCaret, hotspots)
                .Execute();
        }

        public static void AddTypeNamespace(CSharpElementFactory factory, IType type, ITreeNode treeNode)
        {
            ICSharpStatement cSharpStatement = factory.CreateStatement("$0 dummy = default($0);", type);
            cSharpStatement = ModificationUtil.AddChildAfter(treeNode, cSharpStatement);
            ModificationUtil.DeleteChild(cSharpStatement);
        }

        public static bool IsNullValue([CanBeNull] this ICSharpExpression expression)
        {
            if (expression == null)
                return false;
            return expression.ConstantValue.Value == null;
        }

        public static HotspotInfo[] GetGenericTypesHotspots(IList<ITypeUsage> typeArgumentNodes, IPsiModule psiModule, PsiLanguageType languageType)
        {
            var hotspotInfos = new HotspotInfo[typeArgumentNodes.Count];
            for (int index = 0; index < typeArgumentNodes.Count; index++)
            {
                var node = typeArgumentNodes[index];
                var field = new TemplateField("CHANGETYPE" + index,
                    new TypeTemplateExpression(Enumerable.Empty<IType>(), psiModule, languageType), 0);
                hotspotInfos[index] = new HotspotInfo(field, node.GetDocumentRange());
            }
            return hotspotInfos;
        }

        public static IDeclaredType UpdateType(IDeclaredType type, IDeclaredType emptyType, out bool isGeneric)
        {
            isGeneric = false;
            ITypeElement typeElement = type.GetTypeElement();
            if (typeElement != null && typeElement.TypeParameters.Count != 0)
            {
                var types = new IType[typeElement.TypeParameters.Count];
                for (int index = 0; index < typeElement.TypeParameters.Count; index++)
                {
                    types[index] = emptyType;
                }
                type = TypeFactory.CreateType(typeElement, types);
                isGeneric = true;
            }
            return type;
        }

        [CanBeNull]
        public static T GetQualifierExpressionDeclaredElement<T>([CanBeNull] this IReferenceExpression expression)
            where T : class, IDeclaredElement
        {
            if (expression == null || expression.QualifierExpression == null)
                return null;
            var qualifierExpression = expression.QualifierExpression as IReferenceExpression;
            if (qualifierExpression == null)
                return null;
            return qualifierExpression.Reference.GetDeclaredElement<T>();
        }

        [CanBeNull]
        public static T GetDeclaredElement<T>([CanBeNull] this IReferenceExpression expression)
            where T : class, IDeclaredElement
        {
            if (expression == null)
                return null;
            return expression.Reference.GetDeclaredElement<T>();
        }

        [CanBeNull]
        public static T GetDeclaredElement<T>([CanBeNull] this IReference expression)
            where T : class, IDeclaredElement
        {
            if (expression == null)
                return null;
            return expression.Resolve().DeclaredElement as T;
        }

        [CanBeNull]
        public static T GetReferenceDeclaredElement<T>([CanBeNull] this ICSharpInvocationInfo expression)
            where T : class, IDeclaredElement
        {
            if (expression == null)
                return null;
            return expression.Reference.GetDeclaredElement<T>();
        }

        [NotNull]
        public static IList<IPsiModule> GetReferences(this IPsiModule psiModule, IModuleReferenceResolveContext context, bool requiredToolkitReference)
        {
            var psiServices = psiModule.GetPsiServices();
            var references = psiServices.Modules
                .GetModuleReferences(psiModule, context)
                .Select(reference => reference.Module);
            if (!requiredToolkitReference)
                return references.ToList();
            return references
                .Where(module =>
                {
                    var psiAssembly = module as IAssemblyPsiModule;
                    return psiAssembly == null ||
                           (!psiAssembly.Assembly.IsFrameworkAssembly && !psiAssembly.Assembly.IsMscorlib);
                })
                .ToList();
        }

        [NotNull]
        public static IList<IPsiModule> GetRelatedModules(this IPsiModule psiModule, IModuleReferenceResolveContext context)
        {
            var solution = psiModule.GetSolution();
            var psiModules = solution.PsiModules();
            var modules = psiModules.GetSourceModules();
            if (!(psiModule.ContainingProjectModule is IProject))
                modules = modules.Concat(psiModules.GetAssemblyModules());
            return modules
                .Where(module => !Equals(module, psiModule) && module.GetModuleReference(psiModule, context) != null)
                .ToList();
        }

        #endregion
    }
}